﻿using System;
using System.Collections;
using System.Collections.Generic;
using System.ComponentModel;
using System.Threading;
using System.Linq;
using System.Text;
using System.Net;
using System.Net.Sockets;

namespace IPMulticastLib
{

	/// <summary>
	/// Public delegate that allows IPMulticaster clients to register the message received Event.
	/// </summary>
	/// <param name="sender">A reference to the IPMulticaster instance that fired the event.</param>
	/// <param name="e">A MessageReceivedEventArgs containing the message and Timestamp of the multicast message that was received.</param>
	public delegate void MessageReceivedEventHandler(object sender, MessageReceivedEventArgs e);


	/// <summary>
	/// IPMulticaster creates a Sender and Receiver UDPClient instances that creates or joins a IP Multicast group based on the Multicast IP and Port provided.
	/// IP Multicast Addresses must be within the 239.0.0.0/8 range is assigned by RFC 2365 as a locally administered address space with local or organizational scope.
	/// For more information see: http://en.wikipedia.org/wiki/IP_multicast
	/// </summary>
	public class IPMulticaster
	{		
		private IPAddress MulticastIP;
		private int MulticastPort;
		private int MulticastTTL;
		private IPEndPoint MulticastIPEndpoint;
		private UdpClient SendClient;
		private UdpClient ReceiveClient;		
		private static bool messageReceived = false;
		private const string RECEIVETHREADNAME = "IPMulticastReceiver";

		/// <summary>
		/// IPMulticaster clients register the MessageReceived Event to receive MessageReceivedEventArgs containing the message and a timestamp when multicast messages are received.
		/// </summary>
		public event MessageReceivedEventHandler MessageReceived;

		/// <summary>
		/// OnMessageReceived is called when a multicast message is received from the multicast group
		/// </summary>
		/// <param name="e">An instance of the MessageReceivedEventArgs object containing the message and timestamp for the multicast message that was received.</param>
		protected virtual void OnMessageReceived(MessageReceivedEventArgs e)
		{
			if (MessageReceived != null)
				MessageReceived(this, e);
		}

		/// <summary>
		/// IPMulticaster constructor that creates or joins  IP Multicast group based on the Multicast IP and Port provided.
		/// </summary>
		/// <param name="multicastIP">The IP Address of the Multicast group you are creating or joining.</param>
		/// <param name="multicastPort">The Port number of the Multicast group you are creating or joining.</param>
		/// <param name="multicastTTL">The number of </param>
		/// <param name="echo">Should this instance of the library receive messages it sent?</param>
		public IPMulticaster(string multicastIP, int multicastPort, int multicastTTL, bool echo)
		{
			try
			{
				if (multicastIP == null)
				{
					throw new ArgumentNullException("multicastIP");
				}
				if (multicastPort == null)
				{
					throw new ArgumentNullException("multicastPort");
				}
				if (multicastTTL == null)
				{
					throw new ArgumentNullException("multicastTTL");
				}

				MulticastIP = IPAddress.Parse(multicastIP);
				MulticastPort = multicastPort;
				MulticastTTL = multicastTTL;

				IPAddress localIP = IPAddress.Any;
				IPEndPoint ep = new IPEndPoint(localIP, 0);

				SendClient = new UdpClient(ep);
				if (echo)
				{
					SendClient.MulticastLoopback = true;
				}
				SendClient.JoinMulticastGroup(MulticastIP, MulticastTTL);

				ReceiveClient = new UdpClient(MulticastPort);
				MulticastIPEndpoint = new IPEndPoint(MulticastIP, MulticastPort);
				ReceiveClient.JoinMulticastGroup(MulticastIP);

				//Receive messages on a seperate thread.
				Thread receiverThread = new Thread(new ThreadStart(ReceiveMessages));
				receiverThread.Name = RECEIVETHREADNAME;
				receiverThread.IsBackground = true;
				receiverThread.Start();
			}
			catch (Exception ex)
			{
				throw ex;
				//TODO: Add logging.
			}
		}

		/// <summary>
		/// Send connects the Sender UDPClient to the multicast group and sends the message to the group.
		/// </summary>
		/// <param name="message">The message to send to the multicast group.</param>
		public void Send(string message)
		{

			if (message == null)
			{
				throw new ArgumentNullException("message");
			}

			if(message.Length !=0)
			{
				try
				{
					Byte[] sendBytes = Encoding.ASCII.GetBytes(message);
					SendClient.Connect(MulticastIP, MulticastPort);
					SendClient.Send(sendBytes, sendBytes.Length);
				}
				catch (Exception ex)
				{
					//TODO: Add logging.
				}
			}
		}

		/// <summary>
		/// ReceiveCallback is the asynchronus delegate that is called when a message is received.
		/// When a message is received the MessageReceived event is fired.
		/// </summary>
		/// <param name="ar">An instance of a StateObject that contains a reference to the UDPClient and IPEndpoint configured for this multicast group.</param>
		private void ReceiveCallback(IAsyncResult ar)
		{
			try
			{
				UdpClient client = (UdpClient)((StateObject)(ar.AsyncState)).udpClient;
				IPEndPoint endpoint = (IPEndPoint)((StateObject)(ar.AsyncState)).ipEndPoint;

				Byte[] receiveBytes = client.EndReceive(ar, ref endpoint);
				string receiveString = Encoding.ASCII.GetString(receiveBytes);

				//messageQueue.Enqueue(receiveString);
				messageReceived = true;

				MessageReceived(this, new MessageReceivedEventArgs(receiveString));

				//re-enter the ReceiveMessages to receive the next multicast.
				ReceiveMessages();
			}
			catch (SocketException sex)
			{
				//TODO: Add logging.
			}
			catch (Exception ex)
			{
				//TODO: Add logging.
			}
		}

		/// <summary>
		/// ReceiveMessages performs an asynchronus UDPClient.BeginReceive() operation.
		/// </summary>
		private void ReceiveMessages()
		{
			try
			{
				messageReceived = false;
				StateObject state = new StateObject();
				state.ipEndPoint = MulticastIPEndpoint;
				state.udpClient = ReceiveClient;
				ReceiveClient.BeginReceive(new AsyncCallback(ReceiveCallback), state);
				while (!messageReceived)
				{
					Thread.Sleep(100);
				}
			}
			catch (SocketException sex)
			{
				//TODO: Add logging.
			}
			catch (Exception ex)
			{
				//TODO: Add logging.
			}
		}
	}
}
